package org.aplikator.client;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.aplikator.client.data.ClientContext;
import org.aplikator.client.descriptor.ActionDTO;
import org.aplikator.client.descriptor.ApplicationDTO;
import org.aplikator.client.impl.MainMenuTreeViewModel;
import org.aplikator.client.impl.MainMenuTreeViewModel.Category;
import org.aplikator.client.panels.ContentsPanel;
import org.aplikator.client.rpc.AplikatorService;
import org.aplikator.client.rpc.AplikatorServiceAsync;
import org.aplikator.client.rpc.Callback;
import org.aplikator.client.rpc.impl.GetApplication;
import org.aplikator.client.rpc.impl.GetApplicationResponse;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.prefetch.Prefetcher;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.cellview.client.CellTree;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.cellview.client.TreeNode;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;

/**
 * Entry point class of APlikator application (defines <code>onModuleLoad()</code> ).
 */
public class Aplikator implements EntryPoint {

    public interface AplikatorResources extends ClientBundle {
        public static final AplikatorResources INSTANCE = GWT.create(AplikatorResources.class);

        @Source("Aplikator.css")
        public AplikatorCss css();
    }

    public interface AplikatorCss extends CssResource {
        public String menuFrame();

        public String menuHeader();

        public String menuItem();

        public String menuBlock();

        public String dirty();
    }

    private static final Logger LOG = Logger.getLogger(Aplikator.class.getName());

    public static final AplikatorServiceAsync aplikatorService = GWT.create(AplikatorService.class);
    static {
        AplikatorResources.INSTANCE.css().ensureInjected();
    }

    public static final ClientContext clientContext = new ClientContext();

    final SplitLayoutPanel mainPanel = new SplitLayoutPanel();
    final ScrollPanel menuPanel = new ScrollPanel();
    CellTree menu;
    final ContentsPanel contents = new ContentsPanel();
    // final Label loading = new Label("Loading");
    final SingleSelectionModel<ActionDTO> selectionModel = new SingleSelectionModel<ActionDTO>();
    MainMenuTreeViewModel treeModel;

    // Set<ContentWidget> contentWidgets = treeModel.getAllContentWidgets();

    /**
     * Get the token for a given content widget.
     *
     * @return the content widget token.
     */
    public static String getContentWidgetToken(ActionDTO content) {
        if (content == null)
            return null;
        return "!" + content.getId();
    }

    /**
     * Get the token for a given content widget.
     *
     * @return the content widget token.
     */
    public static <C extends ActionDTO> String getContentWidgetToken(Class<C> cwClass) {
        String className = cwClass.getName();
        className = className.substring(className.lastIndexOf('.') + 1);
        return "!" + className;
    }

    /**
     * This is the entry point method.
     */
    public final void onModuleLoad() {
        GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
            public void onUncaughtException(Throwable e) {
                LOG.log(Level.SEVERE, e.getMessage(), e);
            }
        });
        aplikatorService.execute(new GetApplication(), new Callback<GetApplicationResponse>() {
            public void process(GetApplicationResponse resp) {
                ApplicationDTO application = resp.getApplication();
                treeModel = new MainMenuTreeViewModel(application, contents, selectionModel);

                menu = new CellTree(treeModel, null);
                menu.setAnimationEnabled(true);
                menu.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
                // Prefetch examples when opening the Category tree nodes.
                final List<Category> prefetched = new ArrayList<Category>();
                menu.addOpenHandler(new OpenHandler<TreeNode>() {
                    public void onOpen(OpenEvent<TreeNode> event) {
                        Object value = event.getTarget().getValue();
                        if (!(value instanceof Category)) {
                            return;
                        }

                        Category category = (Category) value;
                        if (!prefetched.contains(category)) {
                            prefetched.add(category);
                            Prefetcher.prefetch(category.getSplitPoints());
                        }
                    }
                });

                // Always prefetch.
                Prefetcher.start();

                // Change the history token when a main menu item is selected.
                selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
                    public void onSelectionChange(SelectionChangeEvent event) {
                        ActionDTO selected = selectionModel.getSelectedObject();
                        if (selected != null) {
                            History.newItem(getContentWidgetToken(selected), true);
                        }
                    }
                });

                // Setup a history handler to reselect the associate menu item.
                final ValueChangeHandler<String> historyHandler = new ValueChangeHandler<String>() {
                    public void onValueChange(ValueChangeEvent<String> event) {
                        // Get the content widget associated with the history
                        // token.
                        ActionDTO contentWidget = treeModel.getContentWidgetForToken(event.getValue());
                        if (contentWidget == null) {
                            return;
                        }

                        // Expand the tree node associated with the content.
                        Category category = treeModel.getCategoryForContentWidget(contentWidget);
                        TreeNode node = menu.getRootTreeNode();
                        int childCount = node.getChildCount();
                        for (int i = 0; i < childCount; i++) {
                            if (node.getChildValue(i) == category) {
                                node.setChildOpen(i, true, true);
                                break;
                            }
                        }

                        // Select the node in the tree.
                        selectionModel.setSelected(contentWidget, true);

                        // Display the content widget.
                        contentWidget.getCommand().execute();
                    }
                };
                History.addValueChangeHandler(historyHandler);

                // Show the initial example.
                if (History.getToken().length() > 0) {
                    History.fireCurrentHistoryState();
                } else {
                    // Use the first token available.
                    TreeNode root = menu.getRootTreeNode();
                    TreeNode category = root.setChildOpen(0, true);
                    ActionDTO content = (ActionDTO) category.getChildValue(0);
                    selectionModel.setSelected(content, true);
                }

                menuPanel.setStyleName(AplikatorResources.INSTANCE.css().menuFrame());
                menuPanel.add(menu);
                mainPanel.addWest(menuPanel, 200);
                mainPanel.add(contents);

            }
        });
        RootLayoutPanel.get().add(mainPanel);
    }

}
